﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Xml.Linq;
using System.Xml.Serialization;
using Site3AI.Models;

namespace WxServices.Services
{
    public class WxService : IDisposable
    {
        public class ApiResultRaw
        {
            public bool IsSuccess { get; set; }
            public DateTime CallingTime { get; set; }
            public string Result { get; set; }
        }
        public class ApiResult<T>
        {
            public ApiResultRaw Raw { get; set; }
            public T Result { get; set; }

        }
        public string AppId = "wx63fecf6c0ae4d57c";
        public string AppSecret = "df16eb944322ccbac10de2f955fdc40e";
        public DateTime AccessTokenExpireTime = DateTime.MinValue;
        public string AccessToken = null;
        System.Net.Http.HttpClient Cli;

        const string WEIXINTOKEN = "site3aitokenforweixin";
        const long tickoffsettoposixtimestamp = 621355968000000000;

        Lazy<SiteDbEntities> _db = new Lazy<SiteDbEntities>();
        public SiteDbEntities DB
        {
            get
            {
                return _db.Value;
            }
        }
        public WxService()
        {
            Cli = new System.Net.Http.HttpClient();
            Cli.BaseAddress = new Uri("https://api.weixin.qq.com/cgi-bin/");
            Cli.Dispose();
        }

        public async Task<string> GetAccessToken()
        {
            var av = new { access_token = "", expires_in = 0 };
            var r = await CallApi("token?grant_type=client_credential&appid={0}&secret={1}", av,
                null, () =>
                {
                    return (string.IsNullOrEmpty(AccessToken) && AccessTokenExpireTime > DateTime.Now);
                }
            );
            if (r != null && r.Raw.IsSuccess)
            {

                AccessToken = r.Result.access_token;
                AccessTokenExpireTime = r.Raw.CallingTime.AddSeconds(r.Result.expires_in);

            }
            return AccessToken;
            //for (int i = 0; i < retytimes; i++)
            //{
            //    try
            //    {
            //        if (string.IsNullOrEmpty(AccessToken) && AccessTokenExpireTime > DateTime.Now)
            //        {
            //            return AccessToken;
            //        }

            //        DateTime now = DateTime.Now;
            //        var qt = "token?grant_type=client_credential&appid={0}&secret={1}";
            //        var qurl = string.Format(qt, AppId, AppSecret);
            //        var rs = await Cli.GetStringAsync(qurl);
            //        var at = new { access_token = "", expires_in = 0 };
            //        at = Newtonsoft.Json.JsonConvert.DeserializeAnonymousType(rs, at);
            //        AccessToken = at.access_token;
            //        AccessTokenExpireTime = now.AddSeconds(at.expires_in);
            //        return AccessToken;
            //    }
            //    catch (Exception ex)
            //    {
            //        Console.WriteLine(ex.Message);
            //        if (i < retytimes - 1)
            //        {
            //            new AutoResetEvent(false).WaitOne(retrypersec * 1000);
            //        }
            //        else
            //        {
            //            throw ex;
            //        }
            //    }
            //}
            //return null;


        }

        public async Task<dynamic> GetUerInfo(string useropenid)
        {
            var av = new
            {
                subscribe = 0,
                openid = "",
                nickname = "",
                sex = 0,
                language = "",
                city = "",
                province = "",
                country = "",
                headimgurl = "",
                subscribe_time = 0L
            };
            var r = await CallApi(string.Format("user/info?access_token={0}&openid={1}&lang=zh_CN", GetAccessToken(), useropenid), av);
            return r == null ? null : r.Result;
        }
        public async Task<ApiResult<T>> CallApi<T>(string q, T anonymousObj, Func<T, DateTime, T> procAfterCall = null, Func<bool> skipBeforeCall = null, int retytimes = 3, int retrypersec = 5)
        {
            var r = await CallApiRaw(q, skipBeforeCall, retytimes, retrypersec);
            if (null == r) return null;
            if (!r.IsSuccess) return new ApiResult<T> { Raw = r, Result = default(T) };
            try
            {
                var rawobj = Newtonsoft.Json.JsonConvert.DeserializeAnonymousType(r.Result, anonymousObj);
                if (null == procAfterCall) return new ApiResult<T> { Raw = r, Result = rawobj };
                var newobj = procAfterCall.Invoke(rawobj, r.CallingTime);
                return new ApiResult<T> { Raw = r, Result = newobj };
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        public async Task<ApiResultRaw> CallApiRaw(string q, Func<bool> skipBeforeCall = null, int retytimes = 3, int retrypersec = 5)
        {
            for (int i = 0; i < retytimes; i++)
            {
                try
                {
                    if (null != skipBeforeCall)
                    {
                        if (skipBeforeCall.Invoke()) return new ApiResultRaw { IsSuccess = false };
                    }
                    DateTime now = DateTime.Now;
                    var rs = await Cli.GetStringAsync(q);

                    return new ApiResultRaw { IsSuccess = true, Result = rs, CallingTime = now };
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    if (i < retytimes - 1)
                    {
                        new AutoResetEvent(false).WaitOne(retrypersec * 1000);
                    }
                    else
                    {
                        throw ex;
                    }
                }
            }
            return null;//never execute this
        }

        public bool VerifyWxSignature(string signature, string timestamp, string nonce)
        {
            string[] ks = new[] { WEIXINTOKEN, timestamp, nonce };
            Array.Sort<string>(ks);
            var sig = string.Empty;
            using (var sha1 = System.Security.Cryptography.SHA1.Create())
            {
                var s = sha1.ComputeHash(System.Text.Encoding.ASCII.GetBytes(string.Join("", ks)));
                sig = BitConverter.ToString(s).Replace("-", "");
            }
            var r = sig.Equals(signature, StringComparison.CurrentCultureIgnoreCase);
            return r;
        }
        public long ToPosixTime(DateTime dt)
        {
            return ((System.DateTime.Now.ToUniversalTime().Ticks - tickoffsettoposixtimestamp) / 10000);
        }
        public DateTime FromPosixTime(long timestamp)
        {
            return new DateTime(tickoffsettoposixtimestamp + timestamp * 10000);
        }


        public void Dispose()
        {
            if (_db.IsValueCreated) _db.Value.Dispose();
        }
        public string ToWxResultXml<T>(T obj)
        {
            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
            ns.Add(string.Empty, string.Empty);
            System.Xml.Serialization.XmlSerializer xs = new XmlSerializer(typeof(T));
            XDocument xd = new XDocument();
            using (var xdw = xd.CreateWriter())
            {
                xs.Serialize(xdw, obj, ns);
            }
            xd.Root.RemoveAttributes();
            return xd.ToString();
        }
        public WxServices.Models.WxMessageFull ParseWxMessage(string raw) {
            System.Xml.Serialization.XmlSerializer xs = new XmlSerializer(typeof(WxServices.Models.WxMessageFull));
            using(StringReader sr=new StringReader(raw)){
                return xs.Deserialize(sr) as WxServices.Models.WxMessageFull;
            }
        
        }

    }
}